Skip to content

feature: dynamic (catch-all) workflows - #784

Open
datashaman wants to merge 7 commits into
temporalio:masterfrom
datashaman:feat-dynamic-workflow
Open

feature: dynamic (catch-all) workflows#784
datashaman wants to merge 7 commits into
temporalio:masterfrom
datashaman:feat-dynamic-workflow

Conversation

@datashaman

@datashaman datashaman commented Jul 10, 2026

Copy link
Copy Markdown

What

Lets a PHP worker register a dynamic (catch-all) workflow — invoked for any workflow type name that has no statically-registered handler. The host already registers a single shared WorkflowDefinitionFactory proxy (wDef) under each declared name; this adds the ability to register that same proxy via worker.RegisterDynamicWorkflow when the PHP worker declares a workflow as dynamic.

  • internal/worker_info.go — new WorkflowInfo.Dynamic field, populated from the dynamic key the PHP GetWorkerInfo handshake emits.
  • aggregatedpool/workers.go — when a declared workflow is Dynamic, register it via RegisterDynamicWorkflow (reusing wDef, which forwards the real workflow type name to PHP) and skip named registration for it.
  • aggregatedpool/workers.go — reject multiple dynamic workflows on one task queue before registration, since more than one catch-all would be ambiguous.

Why

Enables Dynamic Workflow support in the PHP SDK — PHP and TypeScript are the only SDKs without it. It lets applications that author workflows at runtime (e.g. UI-driven pipeline/automation builders) give each workflow its own type name — real identity in the Web UI — while a single PHP handler interprets it, with no codegen or per-workflow deploy. Companion PHP change: temporalio/sdk-php#774, which declares the dynamic workflow and emits the dynamic WorkerInfo field.

Dependency — satisfied

temporalio/sdk-go#2449 (issue temporalio/sdk-go#2448) fixed factory-backed dynamic workflow execution and shipped in go.temporal.io/sdk v1.47.0. This PR now pins that release in both Go modules.

Testing

  • go test ./... and go vet ./... clean in both the root and tests Go modules.
  • TestTemporalWorkers_MultipleDynamicWorkflows_ReturnsError verifies that ambiguous dynamic workflow configuration is rejected before registration.
  • End-to-end with a custom rr built from this change + Go SDK v1.47.0 + sdk-php#774: starting an unregistered type (pipeline-blog-publish) is caught by the shared proxy, dispatched to the PHP dynamic handler (which reads the real type via Workflow::getInfo()), and completes → {"dispatchedType":"pipeline-blog-publish"}. Without Go SDK v1.47.0, the same run fails with a WorkflowTaskFailed carrying the reflect panic.

Backwards compatibility

Additive. Dynamic defaults to false (omitted from the handshake JSON), so existing workers register exactly as before.

Allow a PHP worker to register a dynamic (catch-all) workflow, invoked for
any workflow type name that has no statically registered handler. The host
already registers a single shared WorkflowDefinitionFactory proxy (wDef)
under each declared name; when a workflow is declared dynamic, register that
same proxy via worker.RegisterDynamicWorkflow instead (it forwards the real
workflow type name to PHP), and skip named registration for it.

WorkflowInfo gains a Dynamic field, populated from the "dynamic" key the PHP
GetWorkerInfo handshake emits.

This enables Dynamic Workflow support in the PHP SDK (PHP and TypeScript are
the only SDKs without it). Companion change: temporalio/sdk-php.

Depends on temporalio/sdk-go#2449 (issue #2448): RegisterDynamicWorkflow
accepts the factory, but go-sdk's dynamic execution path panics on it until
that fix ships. A go.temporal.io/sdk bump to the first release containing it
is required before dynamic dispatch runs.
@datashaman
datashaman force-pushed the feat-dynamic-workflow branch from b6563aa to d24901b Compare July 10, 2026 12:32
datashaman added a commit to datashaman/sdk-php that referenced this pull request Jul 10, 2026
Add `#[WorkflowMethod(dynamic: true)]` to declare a dynamic (catch-all)
workflow — invoked when the worker receives a workflow whose type name is
not statically registered. WorkflowReader flags the prototype; StartWorkflow
falls back to the dynamic prototype when no named workflow matches; and the
GetWorkerInfo handshake advertises `dynamic` so the RoadRunner temporal
plugin can register a Go dynamic-workflow proxy for it.

As in the other SDKs (Go panics, Python raises), at most one dynamic workflow
may be registered per worker — WorkflowCollection enforces this.

Enables Dynamic Workflow support in the PHP SDK (PHP and TypeScript are the
only SDKs without it).

End-to-end dispatch also requires temporalio/roadrunner-temporal#784 (register
the dynamic proxy) and temporalio/sdk-go#2449 (execute a factory-registered
dynamic workflow).
@datashaman
datashaman marked this pull request as ready for review July 30, 2026 11:20
@datashaman
datashaman requested a review from rustatian as a code owner July 30, 2026 11:20
@rustatian

Copy link
Copy Markdown
Collaborator

Hey @datashaman 👋🏻
Thank you for the PR, good job 👍🏻
Only 1 thing is missing - tests. Could you please add a test to the tests folder with that new dynamic wf stuff?

@rustatian rustatian changed the title Support dynamic (catch-all) workflows feature: dynamic (catch-all) workflows Jul 30, 2026
@rustatian
rustatian requested a review from Copilot July 30, 2026 11:23
@rustatian rustatian added the C-enhancement Category: enhancement. Meaning improvements of current module, transport, etc.. label Jul 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for PHP workers to register a dynamic (catch-all) workflow in the Go host, enabling a single PHP handler to receive otherwise-unregistered workflow type names while preserving the real type name for UI/identity.

Changes:

  • Bump go.temporal.io/sdk to v1.47.0 (root and tests modules) to pick up dynamic-workflow factory support.
  • Extend worker handshake model with WorkflowInfo.Dynamic to mark workflows as catch-all.
  • Register dynamic workflows via worker.RegisterDynamicWorkflow (and skip named registration) when Dynamic is set.

Reviewed changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
go.mod Pins Temporal Go SDK to v1.47.0 in the main module.
go.sum Adds checksums for go.temporal.io/sdk v1.47.0.
tests/go.mod Pins Temporal Go SDK to v1.47.0 in the tests module.
tests/go.sum Adds checksums for go.temporal.io/sdk v1.47.0 in the tests module.
internal/worker_info.go Adds WorkflowInfo.Dynamic to carry the PHP handshake flag.
aggregatedpool/workers.go Registers catch-all workflows using RegisterDynamicWorkflow when Dynamic is set.
Comments suppressed due to low confidence (1)

aggregatedpool/workers.go:160

  • The new dynamic-workflow registration path is not covered by automated tests. This file already has unit tests (e.g., for interceptor/converter resolution and registration panic handling), so it would be good to add a unit/integration test that exercises wf.Dynamic == true and asserts only dynamic registration occurs (and that the named registration is skipped).
			if wf.Dynamic {
				err := registerWorkflow(func() {
					wrk.RegisterDynamicWorkflow(wDef, workflow.DynamicRegisterOptions{})
				}, wf.Name, wi[i].TaskQueue)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread aggregatedpool/workers.go Outdated
datashaman added a commit to datashaman/sdk-php that referenced this pull request Jul 30, 2026
Add `#[WorkflowMethod(dynamic: true)]` to declare a dynamic (catch-all)
workflow — invoked when the worker receives a workflow whose type name is
not statically registered. WorkflowReader flags the prototype; StartWorkflow
falls back to the dynamic prototype when no named workflow matches; and the
GetWorkerInfo handshake advertises `dynamic` so the RoadRunner temporal
plugin can register a Go dynamic-workflow proxy for it.

As in the other SDKs (Go panics, Python raises), at most one dynamic workflow
may be registered per worker — WorkflowCollection enforces this.

Enables Dynamic Workflow support in the PHP SDK (PHP and TypeScript are the
only SDKs without it).

End-to-end dispatch also requires temporalio/roadrunner-temporal#784 (register
the dynamic proxy) and temporalio/sdk-go#2449 (execute a factory-registered
dynamic workflow).
@datashaman

Copy link
Copy Markdown
Author

Thanks @rustatian — agreed, this needs end-to-end coverage.

The integration fixture currently installs released temporal/sdk versions, but dynamic-workflow support is still in temporalio/sdk-php#774 and hasn’t been packaged yet. Pinning this PR’s tests to my sdk-php feature branch would create a circular cross-PR dependency.

My proposed order is to merge/package sdk-php#774 first, then update the fixture here and add a test that starts an unregistered workflow type and verifies the PHP dynamic handler receives the real type name. Would you prefer that, or a temporary feature-branch pin in this PR?

Comment thread aggregatedpool/workers.go Outdated
@rustatian

Copy link
Copy Markdown
Collaborator

Thanks @rustatian — agreed, this needs end-to-end coverage.

The integration fixture currently installs released temporal/sdk versions, but dynamic-workflow support is still in temporalio/sdk-php#774 and hasn’t been packaged yet. Pinning this PR’s tests to my sdk-php feature branch would create a circular cross-PR dependency.

My proposed order is to merge/package sdk-php#774 first, then update the fixture here and add a test that starts an unregistered workflow type and verifies the PHP dynamic handler receives the real type name. Would you prefer that, or a temporary feature-branch pin in this PR?

Thank you. I see that the SDK-PHP changes are more significant. So we can merge RR's part first, since anyway this is a chicken-and-egg problem. You can then write more tests on the SDK-PHP side.

@datashaman

datashaman commented Jul 31, 2026

Copy link
Copy Markdown
Author

Resolved the conflict with the latest master and pushed the merge. I kept the upstream workflow updates and pinned CodeQL v4.37.3 consistently across init/autobuild/analyze.

I also aligned the Go version files on 1.26.5 (following #790 pattern), there was an existing mismatch on master.

Unit tests, go vet, golangci-lint, the updates integration suite, and the H2C test all pass locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-enhancement Category: enhancement. Meaning improvements of current module, transport, etc..

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants